Linux eMMC子系统之主机控制器驱动(host conntroller driver)

您所在的位置:网站首页 embedded controller驱动作用 Linux eMMC子系统之主机控制器驱动(host conntroller driver)

Linux eMMC子系统之主机控制器驱动(host conntroller driver)

2024-07-16 06:34| 来源: 网络整理| 查看: 265

1. 前言

本文是Linux MMC framework的第二篇,将从驱动工程师的角度,介绍MMC host controller driver有关的知识,学习并掌握如何在MMC framework的框架下,编写MMC控制器的驱动程序。同时,通过本篇文章,我们会进一步的理解MMC、SD、SDIO等有关的基础知识。

2. MMC host驱动介绍

MMC的host driver,是用于驱动MMC host控制器的程序,位于“drivers/mmc/host”目录。从大的流程上看,编写一个这样的驱动非常简单,只需要三步:

1)调用mmc_alloc_host,分配一个struct mmc_host类型的变量,用于描述某一个具体的MMC host控制器。

2)根据MMC host控制器的硬件特性,填充struct mmc_host变量的各个字段,例如MMC类型、电压范围、操作函数集等等。

3)调用mmc_add_host接口,将正确填充的MMC host注册到MMC core中。

当然,看着简单,一牵涉到实现细节,还是很麻烦的,后面我们会慢慢分析。

注1:分析MMC host driver的时候,Linux kernel中有大把大把的例子(例如drivers/mmc/host/pxamci.c),大家可尽情参考、学习,不必谦虚(这是学习Linux的最佳方法)。

注2:由于MMC host driver牵涉到具体的硬件controller,分析的过程中需要一些具体的硬件辅助理解,本文将以“X Project”所使用Bubblegum-96平台为例,具体的硬件spec可参考[1]。

3. 主要数据结构 3.1 struct mmc_host

MMC core使用struct mmc_host结构抽象具体的MMC host controller,该结构的定义位于“include/linux/mmc/host.h”中,它既可以用来描述MMC控制器所具有的特性、能力(host driver关心的内容),也保存了host driver运行过程中的一些状态、参数(MMC core关心的内容)。需要host driver关心的部分的具体的介绍如下:

parent,一个struct device类型的指针,指向该MMC host的父设备,一般是注册该host的那个platform设备;

class_dev,一个struct device类型的变量,是该MMC host在设备模型中作为一个“设备”的体现。当然,人如其名,该设备从属于某一个class(mmc_host_class);

ops,一个struct mmc_host_ops类型的指针,保存了该MMC host有关的操作函数集,具体可参考3.2小节的介绍;

pwrseq,一个struct mmc_pwrseq类型的指针,保存了该MMC host电源管理有关的操作函数集,具体可参考3.2小节的介绍;

f_min、f_max、f_init,该MMC host支持的时钟频率范围,最小频率、最大频率以及初始频率;

ocr_avail,该MMC host可支持的操作电压范围(具体可参考include/linux/mmc/host.h中MMC_VDD_开头的定义); 注3:OCR(Operating Conditions Register)是MMC/SD/SDIO卡的一个32-bit的寄存器,其中有些bit指明了该卡的操作电压。MMC host在驱动这些卡的时候,需要和Host自身所支持的电压范围匹配之后,才能正常操作,这就是ocr_avail的存在意义。

ocr_avail_sdio、ocr_avail_sd、ocr_avail_mmc,如果MMC host针对SDIO、SD、MMC等不同类型的卡,所支持的电压范围不同的话,需要通过这几个字段特别指定。否则,不需要赋值(初始化为0);

pm_notify,一个struct notifier_block类型的变量,用于支持power management有关的notify实现;

max_current_330、max_current_300、max_current_180,当工作电压分别是3.3v、3v以及1.8v的时候,所支持的最大操作电流(如果MMC host没有特别的限制,可以不赋值);

caps、caps2,指示该MMC host所支持的功能特性,具体可参考3.4小节的介绍;

pm_caps,mmc_pm_flag_t类型的变量,指示该MMC host所支持的电源管理特性;

max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count、max_busy_timeout,和块设备(如MMC、SD、eMMC等)有关的参数,在古老的磁盘时代,这些参数比较重要。对基于MMC技术的块设备来说,硬件的性能大大提升,这些参数就没有太大的意义了。具体可参考5.2章节有关MMC数据传输的介绍;

lock,一个spin lock,是MMC host driver的私有变量,可用于保护host driver的临界资源;

ios,一个struct mmc_ios类型的变量,用于保存MMC bus的当前配置,具体可参考3.5小节的介绍;

supply,一个struct mmc_supply类型的变量,用于描述MMC系统中的供电信息,具体可参考3.6小节的介绍;

……

private,一个0长度的数组,可以在mmc_alloc_host时指定长度,由host controller driver自行支配。

3.2 struct mmc_host_ops

struct mmc_host_ops抽象并集合了MMC host controller所有的操作函数集,包括:

1)数据传输有关的函数

/* * It is optional for the host to implement pre_req and post_req in * order to support double buffering of requests (prepare one * request while another request is active). * pre_req() must always be followed by a post_req(). * To undo a call made to pre_req(), call post_req() with * a nonzero err condition. */ void    (*post_req)(struct mmc_host *host, struct mmc_request *req,                     int err); void    (*pre_req)(struct mmc_host *host, struct mmc_request *req,                    bool is_first_req); void    (*request)(struct mmc_host *host, struct mmc_request *req);

pre_req和post_req是非必需的,host driver可以利用它们实现诸如双buffer之类的高级功能。

数据传输的主题是struct mmc_request类型的指针,具体可参考3.7小节的介绍。

2)总线参数的配置以及卡状态的获取函数

/* * Avoid calling these three functions too often or in a "fast path", * since underlaying controller might implement them in an expensive * and/or slow way. * * Also note that these functions might sleep, so don't call them * in the atomic contexts! * * Return values for the get_ro callback should be: *   0 for a read/write card *   1 for a read-only card *   -ENOSYS when not supported (equal to NULL callback) *   or a negative errno value when something bad happened * * Return values for the get_cd callback should be: *   0 for a absent card *   1 for a present card *   -ENOSYS when not supported (equal to NULL callback) *   or a negative errno value when something bad happened */ void    (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); int     (*get_ro)(struct mmc_host *host); int     (*get_cd)(struct mmc_host *host);

set_ios用于设置bus的参数(ios,可参考3.5小节的介绍);get_ro可获取card的读写状态(具体可参考上面的注释);get_cd用于检测卡的存在状态。

注4:注释中特别说明了,这几个函数可以sleep,耗时较长,没事别乱用。

3)其它一些非主流函数,都是optional的,用到的时候再去细看即可。

3.3 struct mmc_pwrseq

MMC framework的power sequence是一个比较有意思的功能,它提供一个名称为struct mmc_pwrseq_ops的操作函数集,集合了power on、power off等操作函数,用于控制MMC系统的供电,如下:

struct mmc_pwrseq_ops {         void (*pre_power_on)(struct mmc_host *host);         void (*post_power_on)(struct mmc_host *host);         void (*power_off)(struct mmc_host *host);         void (*free)(struct mmc_host *host); };

struct mmc_pwrseq {         const struct mmc_pwrseq_ops *ops; };

与此同时,MMC core提供了一个通用的pwrseq的管理模块(drivers/mmc/core/pwrseq.c),以及一些简单的pwrseq策略(如drivers/mmc/core/pwrseq_simple.c、drivers/mmc/core/pwrseq_emmc.c),最终的目的是,通过一些简单的dts配置,即可正确配置MMC的供电,例如:

/* arch/arm/boot/dts/omap3-igep0020.dts */ mmc2_pwrseq: mmc2_pwrseq {         compatible = "mmc-pwrseq-simple";         reset-gpios = ,      /* gpio_139 - RESET_N_W */                       ;      /* gpio_138 - WIFI_PDN */ };

/* arch/arm/boot/dts/rk3288-veyron.dtsi  */ emmc_pwrseq: emmc-pwrseq {         compatible = "mmc-pwrseq-emmc";         pinctrl-0 = ;         pinctrl-names = "default";         reset-gpios = ; };

具体的细节,在需要的时候,阅读代码即可,这里不再赘述。

3.4 Host capabilities

通过的caps和caps2两个字段,MMC host driver可以告诉MMC core控制器的一些特性、能力,包括(具体可参考include/linux/mmc/host.h中相关的宏定义,更为细致的使用场景和指南,需要结合实际的硬件,具体分析):

MMC_CAP_4_BIT_DATA,支持4-bit的总线传输; MMC_CAP_MMC_HIGHSPEED,支持“高速MMC时序”; MMC_CAP_SD_HIGHSPEED,支持“高速SD时序”; MMC_CAP_SDIO_IRQ,可以产生SDIO有关的中断; MMC_CAP_SPI,仅仅支持SPI协议(可参考“drivers/mmc/host/mmc_spi.c”中有关的实现); MMC_CAP_NEEDS_POLL,表明需要不停的查询卡的插入状态(如果需要支持热拔插的卡,则需要设置该feature); MMC_CAP_8_BIT_DATA,支持8-bit的总线传输; MMC_CAP_AGGRESSIVE_PM,支持比较积极的电源管理策略(kernel的注释为“Suspend (e)MMC/SD at idle”); MMC_CAP_NONREMOVABLE,表明该MMC控制器所连接的卡是不可拆卸的,例如eMMC; MMC_CAP_WAIT_WHILE_BUSY,表面Host controller在向卡发送命令时,如果卡处于busy状态,是否等待。如果不等待,MMC core在一些流程中(如查询busy状态),需要额外做一些处理; MMC_CAP_ERASE,表明该MMC控制器允许执行擦除命令; MMC_CAP_1_8V_DDR,支持工作电压为1.8v的DDR(Double Data Rate)mode[3]; MMC_CAP_1_2V_DDR,支持工作电压为1.2v的DDR(Double Data Rate)mode[3]; MMC_CAP_POWER_OFF_CARD,可以在启动之后,关闭卡的供电(一般只有在SDIO的应用场景中才可能用到,因为SDIO所连接的设备可能是一个独立的设备); MMC_CAP_BUS_WIDTH_TEST,支持通过CMD14和CMD19进行总线宽度的测试,以便选择一个合适的总线宽度进行通信; MMC_CAP_UHS_SDR12、MMC_CAP_UHS_SDR25、MMC_CAP_UHS_SDR50、MMC_CAP_UHS_SDR104,它们是SD3.0的速率模式,分别表示支持25MHz、50MHz、100MHz和208MHz的SDR(Single Data Rate,相对[3]中的DDR)模式; MMC_CAP_UHS_DDR50,它也是SD3.0的速率模式,表示支持50MHz的DDR(Double Data Rate[3])模式; MMC_CAP_DRIVER_TYPE_A、MMC_CAP_DRIVER_TYPE_C、MMC_CAP_DRIVER_TYPE_D,分别表示支持A/C/D类型的driver strength(驱动能力,具体可参考相应的spec); MMC_CAP_CMD23,表示该controller支持multiblock transfers(通过CMD23); MMC_CAP_HW_RESET,支持硬件reset;

MMC_CAP2_FULL_PWR_CYCLE,表示该controller支持从power off到power on的完整的power cycle; MMC_CAP2_HS200_1_8V_SDR、MMC_CAP2_HS200_1_2V_SDR,HS200是eMMC5.0支持的一种速率模式,200是200MHz的缩写,分别表示支持1.8v和1.2v的SDR模式; MMC_CAP2_HS200,表示同时支持MMC_CAP2_HS200_1_8V_SDR和MMC_CAP2_HS200_1_2V_SDR; MMC_CAP2_HC_ERASE_SZ,支持High-capacity erase size; MMC_CAP2_CD_ACTIVE_HIGH,CD(Card-detect)信号高有效; MMC_CAP2_RO_ACTIVE_HIGH,RO(Write-protect)信号高有效; MMC_CAP2_PACKED_RD、MMC_CAP2_PACKED_WR,允许packed read、packed write; MMC_CAP2_PACKED_CMD,同时支持DMMC_CAP2_PACKED_RD和MMC_CAP2_PACKED_WR; MMC_CAP2_NO_PRESCAN_POWERUP,在scan之前不要上电; MMC_CAP2_HS400_1_8V、MMC_CAP2_HS400_1_2V,HS400是eMMC5.0支持的一种速率模式,400是400MHz的缩写,分别表示支持1.8v和1.2v的HS400模式; MMC_CAP2_HS400,同时支持MMC_CAP2_HS400_1_8V和MMC_CAP2_HS400_1_2V; MMC_CAP2_HSX00_1_2V,同时支持MMC_CAP2_HS200_1_2V_SDR和MMC_CAP2_HS400_1_2V; MMC_CAP2_SDIO_IRQ_NOTHREAD,SDIO的IRQ的处理函数,不能在线程里面执行; MMC_CAP2_NO_WRITE_PROTECT,没有物理的RO管脚,意味着任何时候都是可以读写的; MMC_CAP2_NO_SDIO,在初始化的时候,不会发送SDIO相关的命令(也就是说不支持SDIO模式)。

3.5 struct mmc_ios

struct mmc_ios中保存了MMC总线当前的配置情况,包括如下信息:

1)clock,时钟频率。

2)vdd,卡的供电电压,通过“1 stop指针);如果是pre-defined block count,则需要sbc指针(用于发送SET_BLOCK_COUNT--CMD23命令);

completion,一个struct completion变量,用于等待此次传输完成,host controller driver可以根据需要使用;

done,传输完成时的回调,用于通知传输请求的发起者;

host,对应的mmc host controller指针。

3.7.3 struct mmc_command

struct mmc_command结构抽象了一个MMC command,包括如下内容:

/* include/linux/mmc/core.h */

opcode,Command的操作码,用于标识该命令是哪一个命令,具体可参考相应的spec(例如[2]);

arg,一个Command可能会携带参数,具体可参考相应的spec(例如[2]);

resp[4],Command发出后,如果需要应答,结果保存在resp数组中,该数组是32-bit的,因此最多可以保存128bits的应答;

flags,是一个bitmap,保存该命令所期望的应答类型,例如: MMC_RSP_PRESENT(1 caps & MMC_CAP_SPI) static inline int mmc_card_is_removable(struct mmc_host *host) static inline int mmc_card_keep_power(struct mmc_host *host) static inline int mmc_card_wake_sdio_irq(struct mmc_host *host) static inline int mmc_host_cmd23(struct mmc_host *host) static inline int mmc_boot_partition_access(struct mmc_host *host) static inline int mmc_host_uhs(struct mmc_host *host) static inline int mmc_host_packed_wr(struct mmc_host *host) static inline int mmc_card_hs(struct mmc_card *card) static inline int mmc_card_uhs(struct mmc_card *card) static inline bool mmc_card_hs200(struct mmc_card *card) static inline bool mmc_card_ddr52(struct mmc_card *card) static inline bool mmc_card_hs400(struct mmc_card *card) static inline void mmc_retune_needed(struct mmc_host *host) static inline void mmc_retune_recheck(struct mmc_host *host)

5. MMC host驱动的编写步骤

经过上面章节的描述,相信大家对MMC controller driver有了比较深的理解,接下来驱动的编写就是一件水到渠成的事情了。这里简要描述一下驱动编写步骤,也顺便为本文做一个总结。

5.1 struct mmc_host的填充和注册

编写MMC host驱动的所有工作,都是围绕struct mmc_host结构展开的。在对应的platform driver的probe函数中,通过mmc_alloc_host分配一个mmc host后,我们需要根据controller的实际情况,填充对应的字段。

mmc host中大部分和controller能力/特性有关的字段,可以通过dts配置(然后在代码中调用mmc_of_parse自动解析并填充),举例如下(注意其中红色的部分,都是MMC framework的标准字段):

/* arch/arm/boot/dts/exynos5420-peach-pit.dts */

&mmc_1 {         status = "okay";         num-slots = ;         non-removable;         cap-sdio-irq;         keep-power-in-suspend;         clock-frequency = ;         samsung,dw-mshc-ciu-div = ;         samsung,dw-mshc-sdr-timing = ;         samsung,dw-mshc-ddr-timing = ;         pinctrl-names = "default";         pinctrl-0 = , , , ,                     , , ;         bus-width = ;         cap-sd-highspeed;         mmc-pwrseq = ;         vqmmc-supply = ; };

5.2 数据传输的实现

填充struct mmc_host变量的过程中,工作量最大的,就是对struct mmc_host_ops的实现(毫无疑问!所有MMC host的操作逻辑都封在这里呢!!)。这里简单介绍一下相关的概念,具体的驱动编写步骤,后面文章会结合“X Project”详细描述。

5.2.1 Sectors(扇区)、Blocks(块)以及Segments(段)的理解

我们在3.1小节介绍struct mmc_host的时候,提到了max_seg_size、max_segs、max_req_size、max_blk_size、max_blk_count等参数。这和磁盘设备(块设备)中Sectors、Blocks、Segments等概念有关,下面简单介绍一下:

1)Sectors

Sectors是存储设备访问的基本单位。

对磁盘、NAND等块设备来说,Sector的size是固定的,例如512、2048等。

对存储类的MMC设备来说,按理说也应有固定size的sector。但因为有MMC协议的封装,host驱动以及上面的块设备驱动,不需要关注物理的size。它们需要关注的就是bus上的数据传输单位(具体可参考MMC protocol的介绍[7])。

最后,对那些非存储类的MMC设备来说,完全没有sector的概念了。

2) Blocks

Blocks是数据传输的基本单位,是VFS(虚拟文件系统)抽象出来的概念,是纯软件的概念,和硬件无关。它必须是2的整数倍、不能大于Sectors的单位、不能大于page的长度,一般为512B、2048B或者4096B。

对MMC设备来说,由于协议的封装,淡化了Sector的概念,或者说,MMC设备可以支持一定范围内的任意的Block size。Block size的范围,由两个因素决定: a)host controller的能力,这反映在struct mmc_host结构的max_blk_size字段上。 b)卡的能力,这可以通过MMC command从卡的CSD(Card-Specific Data)寄存器中读出。

3)Segments[8]

块设备的数据传输,本质上是设备上相邻扇区与内存之间的数据传输。通常情况下,为了提升性能,数据传输通过DMA方式。

在磁盘控制器的旧时代,DMA操作都比较简单,每次传输,数据在内存中必须是连续的。现在则不同,很多SOC都支持“分散/聚合”(scatter-gather)DMA操作,这种操作模式下,数据传输可以在多个非连续的内存区域中进行。

对于每个“分散/聚合”DMA操作,块设备驱动需要向控制器发送: a)初始扇区号和传输的总共扇区数 b)内存区域的描述链表,每个描述都包含一个地址和长度。不同的描述之间,可以在物理上连续,也可以不连续。

控制器来管理整个数据传输,例如:在读操作中,控制器从块设备相邻的扇区上读取数据,然后将数据分散存储在内存的不同区域。

这里的每个内存区域描述(物理连续的一段内存,可以是一个page,也可以是page的一部分),就称作Segment。一个Segment包含多个相邻扇区。

最后,利用“分散/聚合”的DMA操作,一次数据传输可以会涉及多个segments。

理解了Segment的概念之后,max_seg_size和max_segs两个字段就好理解了:

虽然控制器支持“分散/聚合”的DMA操作,但物理硬件总有限制,例如最大的Segment size(也即一个内存描述的最大长度),最多支持的segment个数(max_segs)等。

5.2.2 struct mmc_data中的sg

我们在3.7.4小节介绍struct mmc_data时,提到了scatterlist的概念。结合上面Segment的解释,就很好理解了:

MMC core提交给MMC host driver的数据传输请求,是一个struct scatterlist链表(也即内存区域的描述链表),也可以理解为是一个个的Segment(Segment的个数保存在sg_len变量中了)。

每个Segment是一段物理地址连续的内存区域,所有的Segments对应了MMC设备中连续的Sector(或者说Block,初始扇区号和传输的总共扇区数已经在之前的MMC command中指定了。

host driver在接收到这样的数据传输请求之后,需要调用dma_map_sg将这些Segment映射出来(获得相应的物理地址),以便交给MMC controller传输。

当然,相邻两个Segment的物理地址可能是连续(或者其它原因),map的时候可能会将两个Segment合成一个。因此可供MMC controller传输的内存片可能少于sg_len(具体要看dma_map_sg的返回值,可将结果保存在sg_count中)。

最后,如何实施传输,则要看具体的MMC controller的硬件实现(可能涉及DMA操作),后面文章再详细介绍。

6. 参考文档

[1] SoC_bubblegum96.pdf

[2] JESD84-A44.pdf

[3] DDR mode, https://en.wikipedia.org/wiki/Double_data_rate

[4] http://www.hjreggel.net/cardspeed/cs_sdxc.html

[5] regulator framework,http://www.wowotech.net/tag/regulator

[6] MMC/SD/SDIO介绍

[7] eMMC 原理 4 :总线协议

[8] http://www.ilinuxkernel.com/files/Linux.Generic.Block.Layer.pdf

 

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3